//@author A0106044W
package logic;

import java.util.logging.Level;

import commandInfo.CommandInfo;
import commandInfo.EditCommandInfo;
import storage.Date;
import storage.DateRange;
import storage.Time;
import storage.TimeRange;
import flexiCommands.Command;
import storage.Task;

/**
 * 
 *
 * 	       Sothearith Sreang
 *         All of the Logic part is written by me.
 * 
 */

public class EditHandler extends OperationHandler {
	
	Task task;
	Task editInfoTask;
	
	private static final String SUCCESSFUL_EDIT_MESSAGE = "task with description \"%s\" has been updated.";
	private static final String SUCCESSFUL_UNDO_MESSAGE = "";
	
	private final int MAXIMUM_YEAR = 2999;
	private final int MAXIMUM_HOUR = 23;
	private final int MAXIMUM_MINUTE = 59;
	private final int MAXIMUM_SECOND = 59;
	
	private final boolean INITIALIZE_CONDITION = false;
	private final boolean IS_VALID = true;
	private final boolean IS_INVALID = false;
	
	private final String START_DATE_FEEDBACK_TEMPLATE = "Start date (%s) should not be later than end date (%s)";
	private final String END_DATE_FEEDBACK_TEMPLATE = "End date (%s) should not be earlier than start date (%s)";
	private final String START_TIME_FEEDBACK_TEMPLATE = "Start time (%s) should not be later than end time (%s)";
	private final String END_TIME_FEEDBACK_TEMPLATE = "End time (%s) should not be earlier than start date (%s)";
	
	// Two public constructors
	
	//for normal edit operation
	public EditHandler(Command userCommandP, Task taskAtt) throws Exception {
		super();
		task = taskAtt;
		setEditInfo(userCommandP);
	}

	//for undo/redo operation for previous edit operation
	public EditHandler(CommandInfo commandInfoP){
		super();
		Task taskClone = commandInfoP.getUpdatedTask();
		task = super.getTask(taskClone);
		assert task != null;
		editInfoTask = commandInfoP.getPreviousTask();
	}
	
	
	// This method is called by user command's edit operation
	public String execute(){
		
		Task oldTask = getTaskClone();
		
		editTask(editInfoTask);
		super.commandInfo = new EditCommandInfo(task, oldTask); 
		
		String feedback = String.format(SUCCESSFUL_EDIT_MESSAGE, task.getDescription());
		
		return feedback;
	}	

	// This method is called by undo operation for previous edit operation
	public String undo(){
		Task oldTask = null;
		
		try{
			oldTask = task.cloneSelf();
		} catch (Exception e) {
			Logic.logger.log(Level.SEVERE, e.getMessage());
		}
		
		editTask(editInfoTask);
		super.commandInfo = new EditCommandInfo(task, oldTask);
		
		return SUCCESSFUL_UNDO_MESSAGE;
	}
	
	// next level abstraction
	
	private void editTask(Task editInfoTask) {
		String description = editInfoTask.getDescription();
		assert description != null;
		DateRange dateRange = editInfoTask.getDateRange();
		assert dateRange != null;
		TimeRange timeRange = editInfoTask.getTimeRange();
		assert timeRange != null;
		
		task.setDescription(description);
		task.getDateRange().setEndDate(dateRange.getEndDate());
		task.getDateRange().setStartDate(dateRange.getStartDate());
		task.getTimeRange().setEndTime(timeRange.getEndTime());
		task.getTimeRange().setStartTime(timeRange.getStartTime());
	}
	
	void setEditInfo(Command userCommandP) throws Exception{
		editInfoTask = getTaskClone();
		
		setUpDescriptionForEditInfo(userCommandP);
		setUpDateRangeForEditInfo(userCommandP);
		setUpTimeRangeForEditInfo(userCommandP);
	}

	// next level abstraction
	
	private Task getTaskClone() {
		Task taskClone = null;
		
		try {
			taskClone = task.cloneSelf();
		} catch (Exception e) {
			Logic.logger.log(Level.SEVERE, e.getMessage());
		}
		
		return taskClone;
	}

	
	private void setUpDescriptionForEditInfo(Command userCommandP) {
		String description = userCommandP.getDescription();
		
		if(description != null) {
			editInfoTask.setDescription(description);
		}
	}
	
	private void setUpDateRangeForEditInfo(Command userCommandP) throws Exception {
		DateRange dateRange = userCommandP.getDateRange();
		
		setUpEndDateForEditInfo(dateRange);
		setUpStartDateForEditInfo(dateRange);
	}
	
	private void setUpTimeRangeForEditInfo(Command userCommandP) throws Exception {
		TimeRange timeRange = userCommandP.getTimeRange();
		
		setUpEndTimeForEditInfo(timeRange);
		setUpStartTimeForEditInfo(timeRange);
	}

	// next level abstraction
	
	private void setUpEndTimeForEditInfo(TimeRange timeRange) throws Exception {
		Time endTime = timeRange.getEndTime();
		Time maximumTime = null;
		
		try{
			maximumTime = new Time(MAXIMUM_HOUR, MAXIMUM_MINUTE, MAXIMUM_SECOND);
		} catch (Exception e) {
			Logic.logger.log(Level.SEVERE, "Problem with storage.Time constructor");
		}
		
		if(!endTime.isEmpty() && endTime.compareTo(maximumTime) != 0) {
			if(checkInvalidEndTime(editInfoTask, endTime)) {
				boolean isSuccess = editInfoTask.getTimeRange().setEndTime(endTime);
				if(!isSuccess) {
					throw new Exception("Wrong end time");
				}
			} else {
				Time startTime = editInfoTask.getTimeRange().getStartTime();
				String feedback = giveInvalidTimeFeedback(END_TIME_FEEDBACK_TEMPLATE, endTime, startTime);
				throw new Exception(feedback);
			}
		}
	}

	private void setUpStartTimeForEditInfo(TimeRange timeRange) throws Exception {
		Time startTime = timeRange.getStartTime();
		
		if(!startTime.isEmpty()) {
			if(checkInvalidStartTime(editInfoTask, startTime)) {
				boolean isSuccess = editInfoTask.getTimeRange().setStartTime(startTime);
				if(!isSuccess) {
					throw new Exception("Wrong start time");
				}
			} else {
				Time endTime = editInfoTask.getTimeRange().getEndTime();
				String feedback = giveInvalidTimeFeedback(START_TIME_FEEDBACK_TEMPLATE, startTime, endTime);
				throw new Exception(feedback);
			}
		}
	}

	private void setUpEndDateForEditInfo(DateRange dateRange) throws Exception {
		Date endDate = dateRange.getEndDate();	
		Date maximumDate = new Date(MAXIMUM_YEAR);
		
		if(!endDate.isEmpty() && endDate.compareTo(maximumDate) != 0) {
			Date startDate = editInfoTask.getDateRange().getStartDate();
			boolean isEndDateLater = endDate.compareTo(startDate) >= 0;
			
			if(isEndDateLater) {
				boolean isSuccess = editInfoTask.getDateRange().setEndDate(endDate);
				if(!isSuccess) {
					throw new Exception("Wrong end date");
				}
			} else {
				String feedback = giveInvalidDateFeedback(END_DATE_FEEDBACK_TEMPLATE, endDate, startDate);
				throw new Exception(feedback);
			}
		}
	}

	private void setUpStartDateForEditInfo(DateRange dateRange) throws Exception {
		Date startDate = dateRange.getStartDate();
		
		if(!startDate.isEmpty()) {
			Date endDate = editInfoTask.getDateRange().getEndDate();
			if(isStartDateValid(editInfoTask, startDate)) {
				boolean isSuccess = editInfoTask.getDateRange().setStartDate(startDate);
				if(!isSuccess) {
					throw new Exception("Wrong start date");
				}
			} else {
				String feedback = giveInvalidDateFeedback(START_DATE_FEEDBACK_TEMPLATE, startDate, endDate);
				throw new Exception(feedback);
			}
		}
	}

	// next level abstraction 
	private String giveInvalidDateFeedback(String template, Date date1, Date date2) {
		String date1Form = Helper.getDateForm(date1);
		String date2Form = Helper.getDateForm(date2);
		String feedback = String.format(template, date1Form, date2Form);
		
		return feedback;
	}

	private boolean checkInvalidStartTime(Task editInfoTask, Time startTime) {
		boolean isStartTimeValid = INITIALIZE_CONDITION;
		
		Date endDate = editInfoTask.getDateRange().getEndDate();
		Date startDate = editInfoTask.getDateRange().getStartDate();
		assert startDate.compareTo(endDate) <= 0;

		int isStartDateEarlier = startDate.compareTo(endDate); 
		if(isStartDateEarlier < 0) {
			isStartTimeValid = IS_VALID;
		} else if(isStartDateEarlier == 0) {
			Time endTime = editInfoTask.getTimeRange().getEndTime();
			
			int isStartTimeEarlier = startTime.compareTo(endTime);
			if(isStartTimeEarlier < 0) {
				isStartTimeValid = IS_VALID;
			} else {
				isStartTimeValid = IS_INVALID;
			}
		}
		
		return isStartTimeValid;
	}
	
	private boolean checkInvalidEndTime(Task editInfoTask, Time endTime) {
		boolean isEndTimeValid = INITIALIZE_CONDITION;
		
		Date endDate = editInfoTask.getDateRange().getEndDate();
		Date startDate = editInfoTask.getDateRange().getStartDate();
		assert startDate.compareTo(endDate) <= 0;

		int isStartDateEarlier = startDate.compareTo(endDate); 
		if(isStartDateEarlier < 0) {
			isEndTimeValid = IS_VALID;
		} else if(isStartDateEarlier == 0) {
			Time startTime = editInfoTask.getTimeRange().getStartTime();
			
			int isEndTimeLater = endTime.compareTo(startTime);
			if(isEndTimeLater > 0) {
				isEndTimeValid = IS_VALID;
			} else {
				isEndTimeValid = IS_INVALID;
			}
		}
		
		return isEndTimeValid;
	}
	
	private boolean isStartDateValid(Task editInfoTask, Date startDate) {
		boolean isStartDateValid = INITIALIZE_CONDITION;
		
		Date endDate = editInfoTask.getDateRange().getEndDate();
		
		int isStartDateEarlier = startDate.compareTo(endDate);
		if(isStartDateEarlier < 0) {
			isStartDateValid = IS_VALID;
		} else if(isStartDateEarlier > 0) {
			isStartDateValid = IS_INVALID;
		} else {
			Time startTime = editInfoTask.getTimeRange().getStartTime();
			Time endTime = editInfoTask.getTimeRange().getEndTime();
			
			if(startTime.isEmpty() || endTime.isEmpty()) {
				isStartDateValid = IS_VALID;
			} else if(!startTime.isEmpty() && !endTime.isEmpty()) {
				boolean isStartTimeEarlier = startTime.compareTo(endTime) < 0;
			
				if(isStartTimeEarlier) {
					isStartDateValid = IS_VALID;
				} else {
					isStartDateValid = IS_INVALID;
				}
			}
		}
		
		return isStartDateValid;
	}
	
	private String giveInvalidTimeFeedback(String template, Time time1, Time time2) {	
		String time1Form = Helper.getTimeForm(time1);
		String time2Form = Helper.getTimeForm(time2);
		String feedback = String.format(template, time1Form, time2Form);
		
		return feedback;
	}
}
